/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007-2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.projects.toolbox_basics_project.spring.manager.impl;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import java.util.Map.Entry;

import org.springframework.transaction.annotation.Transactional;

import com.genia.toolbox.basics.bean.DataContainer;
import com.genia.toolbox.basics.exception.technical.TechnicalException;
import com.genia.toolbox.basics.exception.technical.TechnicalFileNotFoundException;
import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.basics.manager.ExceptionManager;
import com.genia.toolbox.basics.manager.StreamManager;
import com.genia.toolbox.basics.manager.XmlMappingManager;
import com.genia.toolbox.persistence.jcr.manager.DataContainerManager;
import com.genia.toolbox.projects.toolbox_basics_project.bean.TemplateDescriptor;
import com.genia.toolbox.projects.toolbox_basics_project.bean.TemplateTypeDescriptor;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.I18nDynamicString;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.I18nString;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.Language;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.Template;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.TemplateType;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.impl.I18nDynamicStringImpl;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.impl.I18nStringImpl;
import com.genia.toolbox.projects.toolbox_basics_project.business.dao.TemplateDAO;
import com.genia.toolbox.projects.toolbox_basics_project.spring.manager.LanguageManager;
import com.genia.toolbox.projects.toolbox_basics_project.spring.manager.TemplateManager;
import com.genia.toolbox.projects.toolbox_basics_project.web.command.TemplateCommand;

/**
 * implementation of {@link TemplateManager}.
 */
public class TemplateManagerImpl
    implements TemplateManager
{

  /**
   * the {@link XmlMappingManager} to handle template descriptions.
   */
  private XmlMappingManager xmlMappingManager;

  /**
   * the {@link ExceptionManager} to use.
   */
  private ExceptionManager exceptionManager;

  /**
   * the {@link StreamManager} to use.
   */
  private StreamManager streamManager;

  /**
   * the {@link TemplateDAO} to use.
   */
  private TemplateDAO templateDAO;

  /**
   * the {@link LanguageManager} to use.
   */
  private LanguageManager languageManager;

  /**
   * the {@link DataContainerManager} to use.
   */
  private DataContainerManager dataContainerManager;



  /**
   * getter for the exceptionManager property.
   * 
   * @return the exceptionManager
   */
  public ExceptionManager getExceptionManager()
  {
    return exceptionManager;
  }



  /**
   * setter for the exceptionManager property.
   * 
   * @param exceptionManager
   *          the exceptionManager to set
   */
  public void setExceptionManager(ExceptionManager exceptionManager)
  {
    this.exceptionManager = exceptionManager;
  }



  /**
   * returns the {@link TemplateDescriptor} contained in the template bundle
   * given in argument.
   * 
   * @param templateDataContainer
   *          the {@link DataContainer} containing the template bundle
   * @return the {@link TemplateDescriptor} contained in the template bundle
   *         given in argument
   * @throws TechnicalException
   *           if an error occured
   */
  private TemplateDescriptor getTemplateDescriptor(DataContainer templateDataContainer)
      throws TechnicalException
  {
    return getDescriptor(TemplateDescriptor.class, templateDataContainer, TEMPLATE_DESCRIPTOR_FILE);
  }



  /**
   * returns the descriptor object that is described by the file given in
   * argument contained in the template bundle given in argument.
   * 
   * @param <T>
   *          the type of the expected descriptor object
   * @param clazz
   *          the expected class of the descriptor object
   * @param templateDataContainer
   *          the {@link DataContainer} containing the template bundle
   * @param descriptorFile
   *          the location in the archive of the file that must generate the
   *          descriptor object
   * @return the descriptor object that is described by the file given in
   *         argument contained in the template bundle given in argument
   * @throws TechnicalException
   *           if an error occured
   */
  @SuppressWarnings("unchecked")
  private <T> T getDescriptor(Class<T> clazz, DataContainer templateDataContainer, String descriptorFile)
      throws TechnicalException
  {
    InputStream templateDescriptorInputStream = getStreamManager().getInputStreamFromZip(templateDataContainer.getInputStream(), descriptorFile);
    if (templateDescriptorInputStream == null) {
      throw new TechnicalFileNotFoundException(new FileNotFoundException("Unable to retrieve the configuration file (" + descriptorFile + ") from the template bundle."));
    }
    Object res = getXmlMappingManager().getUnmarshaller().unmarshal(templateDescriptorInputStream);
    if (res == null || !clazz.isAssignableFrom(res.getClass())) {
      throw new TechnicalIOException(new IOException("Unable to parse the configuration file (" + descriptorFile + ") from the template bundle."));
    }
    return (T) res;
  }



  /**
   * returns the {@link TemplateTypeDescriptor} contained in the template bundle
   * given in argument.
   * 
   * @param templateDataContainer
   *          the {@link DataContainer} containing the template bundle
   * @return the {@link TemplateTypeDescriptor} contained in the template bundle
   *         given in argument
   * @throws TechnicalException
   *           if an error occured
   */
  private TemplateTypeDescriptor getTemplateTypeDescriptor(DataContainer templateDataContainer)
      throws TechnicalException
  {
    return getDescriptor(TemplateTypeDescriptor.class, templateDataContainer, TEMPLATE_TYPE_DESCRIPTOR_FILE);
  }



  /**
   * import a {@link DataContainer} containing a template into the application.
   * 
   * @param templateDataContainer
   *          the {@link DataContainer} containing the template
   * @throws TechnicalException
   *           if an error occured
   * @see com.genia.toolbox.projects.toolbox_basics_project.spring.manager.TemplateManager#importTemplate(com.genia.toolbox.basics.bean.DataContainer)
   */
  @Transactional(rollbackFor = TechnicalException.class)
  public void importTemplate(DataContainer templateDataContainer)
      throws TechnicalException
  {
    TemplateDescriptor templateDescriptor = getTemplateDescriptor(templateDataContainer);
    TemplateTypeDescriptor templateTypeDescriptor = getTemplateTypeDescriptor(templateDataContainer);

    if (templateTypeDescriptor.getReferenceName() == null) {
      throw new TechnicalIOException(new IOException("Unable to retrieve template type descriptor from the template bundle."));
    }

    TemplateType templateType = getTemplateDAO().getOrCreateTemplateType(templateTypeDescriptor.getReferenceName());
    Template template = null;
    if (templateDescriptor.getReferenceName() == null || TemplateDAO.ROOT_TEMPLATE_REFERENCE_NAME.equals(templateDescriptor.getReferenceName())) {
      template = getTemplateDAO().getOrCreateTemplate(templateType, null);
    }
    else {
      template = getTemplateDAO().getOrCreateTemplate(templateType, templateDescriptor.getReferenceName());
      template.setParentTemplate(getTemplateDAO().getOrCreateTemplate(templateType, templateDescriptor.getParentReferenceName()));
    }
    I18nDynamicString name = template.getName();
    if (name == null) {
      name = new I18nDynamicStringImpl();
    }
    for (Entry<Locale, String> descriptionEntry : templateDescriptor.getNames().entrySet()) {
      Language language = getLanguageManager().getLanguage(descriptionEntry.getKey());
      if (language != null) {
        I18nString i18nString = name.getI18nValues().get(language);
        if (i18nString == null) {
          i18nString = new I18nStringImpl();
          i18nString.setLanguage(language);
        }
        i18nString.setValue(descriptionEntry.getValue());
        name.addI18nValue(i18nString);
      }
    }

    getDataContainerManager().uploadZipDataContainer(template.getUuid(), templateDataContainer);
    getTemplateDAO().updateTemplate(template);
  }



  /**
   * getter for the streamManager property.
   * 
   * @return the streamManager
   */
  public StreamManager getStreamManager()
  {
    return streamManager;
  }



  /**
   * setter for the streamManager property.
   * 
   * @param streamManager
   *          the streamManager to set
   */
  public void setStreamManager(StreamManager streamManager)
  {
    this.streamManager = streamManager;
  }



  /**
   * getter for the templateDAO property.
   * 
   * @return the templateDAO
   */
  public TemplateDAO getTemplateDAO()
  {
    return templateDAO;
  }



  /**
   * setter for the templateDAO property.
   * 
   * @param templateDAO
   *          the templateDAO to set
   */
  public void setTemplateDAO(TemplateDAO templateDAO)
  {
    this.templateDAO = templateDAO;
  }



  /**
   * getter for the languageManager property.
   * 
   * @return the languageManager
   */
  public LanguageManager getLanguageManager()
  {
    return languageManager;
  }



  /**
   * setter for the languageManager property.
   * 
   * @param languageManager
   *          the languageManager to set
   */
  public void setLanguageManager(LanguageManager languageManager)
  {
    this.languageManager = languageManager;
  }



  /**
   * getter for the dataContainerManager property.
   * 
   * @return the dataContainerManager
   */
  public DataContainerManager getDataContainerManager()
  {
    return dataContainerManager;
  }



  /**
   * setter for the dataContainerManager property.
   * 
   * @param dataContainerManager
   *          the dataContainerManager to set
   */
  public void setDataContainerManager(DataContainerManager dataContainerManager)
  {
    this.dataContainerManager = dataContainerManager;
  }



  /**
   * returns the {@link DataContainer} associated to the given path for the
   * given {@link Template}. This method will look to parent {@link Template}s
   * if the given path does not exist in the given {@link Template}.
   * 
   * @param template
   *          the {@link Template} to use
   * @param path
   *          the path of the data to retrieve
   * @return the {@link DataContainer} representing the datas at the given path
   *         in the given template or <code>null</code> if no such data exists
   * @throws TechnicalException
   *           if an error occured
   * @see com.genia.toolbox.projects.toolbox_basics_project.spring.manager.TemplateManager#getDataContainer(com.genia.toolbox.projects.toolbox_basics_project.bean.model.Template,
   *      java.lang.String)
   */
  public DataContainer getDataContainer(Template template, String path)
      throws TechnicalException
  {
    if (template == null) {
      return null;
    }
    DataContainer res = getDataContainerManager().getDataContainerFromDirectory(template.getUuid(), path);
    if (res != null || template.getParentTemplate() == null || TemplateDAO.ROOT_TEMPLATE_REFERENCE_NAME.equals(template.getReferenceName())) {
      return res;
    }
    return getDataContainer(template.getParentTemplate(), path);
  }



  /**
   * returns the {@link DataContainer} associated to the given path for the
   * {@link Template} described by the {@link TemplateCommand}.
   * 
   * @param templateCommand
   *          the {@link TemplateCommand} describing the {@link Template}.
   * @param path
   *          the path of the data to retrieve
   * @return the {@link DataContainer} associated to the given path for the
   *         {@link Template} described by the {@link TemplateCommand} or
   *         <code>null</code> if no such data exists
   * @throws TechnicalException
   *           if an error occured
   * @see com.genia.toolbox.projects.toolbox_basics_project.spring.manager.TemplateManager#getDataContainer(com.genia.toolbox.projects.toolbox_basics_project.web.command.TemplateCommand,
   *      java.lang.String)
   */
  public DataContainer getDataContainer(TemplateCommand templateCommand, String path)
      throws TechnicalException
  {
    return getDataContainer(getTemplate(templateCommand), path);
  }



  /**
   * returns the {@link Template} described by the given {@link TemplateCommand}
   * or <code>null</code> if no such template exists.
   * 
   * @param templateCommand
   *          the {@link TemplateCommand} describing the {@link Template}.
   * @return the {@link Template} described by the given {@link TemplateCommand}
   *         or <code>null</code> if no such template exists
   * @throws TechnicalException
   *           if an error occured
   */
  private Template getTemplate(TemplateCommand templateCommand)
      throws TechnicalException
  {
    if (templateCommand.getTemplateTypeName() == null) {
      return null;
    }
    TemplateType templateType = getTemplateDAO().getTemplateType(templateCommand.getTemplateTypeName());
    if (templateType == null) {
      return null;
    }
    return getTemplateDAO().getTemplate(templateType, templateCommand.getTemplateName());
  }



  /**
   * returns a {@link DataContainer} associated to a file contained in the given
   * path for the given {@link Template}. This method will look to parent
   * {@link Template}s if the given path does not exist in the given
   * {@link Template}.
   * 
   * @param template
   *          the {@link Template} to use
   * @param path
   *          the path of the data to retrieve
   * @return a {@link DataContainer} associated to a file contained in the given
   *         path in the given template or <code>null</code> if no such data
   *         exists
   * @throws TechnicalException
   *           if an error occured
   * @see com.genia.toolbox.projects.toolbox_basics_project.spring.manager.TemplateManager#getRandomDataContainer(com.genia.toolbox.projects.toolbox_basics_project.bean.model.Template,
   *      java.lang.String)
   */
  public DataContainer getRandomDataContainer(Template template, String path)
      throws TechnicalException
  {
    if (template == null) {
      return null;
    }
    DataContainer res = getDataContainerManager().getRandomDataContainerFromDirectory(template.getUuid(), path);
    if (res != null || template.getParentTemplate() == null || TemplateDAO.ROOT_TEMPLATE_REFERENCE_NAME.equals(template.getReferenceName())) {
      return res;
    }
    return getRandomDataContainer(template.getParentTemplate(), path);
  }



  /**
   * returns a {@link DataContainer} associated to a file contained in the given
   * path for the {@link Template} described by the {@link TemplateCommand}.
   * 
   * @param templateCommand
   *          the {@link TemplateCommand} describing the {@link Template}.
   * @param path
   *          the path of the data to retrieve
   * @return a {@link DataContainer} associated to a file contained in the given
   *         path for the {@link Template} described by the
   *         {@link TemplateCommand} or <code>null</code> if no such data
   *         exists
   * @throws TechnicalException
   *           if an error occured
   * @see com.genia.toolbox.projects.toolbox_basics_project.spring.manager.TemplateManager#getRandomDataContainer(com.genia.toolbox.projects.toolbox_basics_project.web.command.TemplateCommand,
   *      java.lang.String)
   */
  public DataContainer getRandomDataContainer(TemplateCommand templateCommand, String path)
      throws TechnicalException
  {
    return getRandomDataContainer(getTemplate(templateCommand), path);
  }



  /**
   * getter for the xmlMappingManager property.
   * 
   * @return the xmlMappingManager
   */
  public XmlMappingManager getXmlMappingManager()
  {
    return xmlMappingManager;
  }



  /**
   * setter for the xmlMappingManager property.
   * 
   * @param xmlMappingManager
   *          the xmlMappingManager to set
   */
  public void setXmlMappingManager(XmlMappingManager xmlMappingManager)
  {
    this.xmlMappingManager = xmlMappingManager;
  }

}
